// main.ts
/**
 * 文件传输服务端
 * 基于Node.js 23和TypeScript实现
 * 支持大文件分段传输，按照特定通信格式解析数据
 * 
 * 通信格式：
 * 1. 文件夹头：
 *    - 1字节：协议版本号(0x01)
 *    - 2字节：文件夹名长度(大端序)
 *    - N字节：文件夹名(UTF8编码)
 *    - 8字节：文件总数(大端序)
 * 
 * 2. 文件块：
 *    - 1字节：文件标识(0x02)
 *    - 2字节：相对路径长度(大端序)
 *    - N字节：相对路径(UTF8编码)
 *    - 8字节：文件大小(大端序)
 *    - M字节：文件内容
 */

import * as net from 'net';
import * as fs from 'fs';
import * as path from 'path';

// 基础配置
const PORT = 3000;
const BASE_DIR = path.resolve('./received_files');

// 确保基础目录存在
if (!fs.existsSync(BASE_DIR)) {
    fs.mkdirSync(BASE_DIR, { recursive: true });
}

// 文件元数据接口
interface FileMeta {
    path: string;      // 相对路径
    size: number;      // 文件大小
    received: number;  // 已接收字节数
    writeStream?: fs.WriteStream; // 文件写入流
}

// 缓冲区管理类
class BufferManager {
    private buffer: Buffer = Buffer.alloc(0);
    
    /**
     * 获取当前缓冲区长度
     */
    get length(): number {
        return this.buffer.length;
    }
    
    /**
     * 追加数据到缓冲区
     * @param data 要追加的数据
     */
    append(data: Buffer): void {
        this.buffer = Buffer.concat([this.buffer, data]);
    }
    
    /**
     * 消费指定长度的数据
     * @param length 要消费的字节数
     */
    consume(length: number): void {
        if (length > 0 && length <= this.buffer.length) {
            this.buffer = this.buffer.slice(length);
        }
    }
    
    /**
     * 读取指定长度的数据，但不从缓冲区移除
     * @param length 要读取的字节数
     * @returns 读取的数据，如果缓冲区不足则返回null
     */
    peek(length: number): Buffer | null {
        if (this.buffer.length < length) return null;
        return this.buffer.slice(0, length);
    }
    
    /**
     * 读取并消费指定长度的数据
     * @param length 要读取的字节数
     * @returns 读取的数据，如果缓冲区不足则返回null
     */
    read(length: number): Buffer | null {
        const data = this.peek(length);
        if (data) {
            this.consume(length);
        }
        return data;
    }
    
    /**
     * 读取一个字节
     * @returns 读取的字节，如果缓冲区为空则返回null
     */
    readUInt8(): number | null {
        const data = this.read(1);
        return data ? data[0] : null;
    }
    
    /**
     * 读取两个字节的无符号整数(大端序)
     * @returns 读取的整数，如果缓冲区不足则返回null
     */
    readUInt16BE(): number | null {
        const data = this.read(2);
        return data ? data.readUInt16BE(0) : null;
    }
    
    /**
     * 读取八个字节的无符号整数(大端序)
     * @returns 读取的整数，如果缓冲区不足则返回null
     */
    readBigUInt64BE(): bigint | null {
        const data = this.read(8);
        return data ? data.readBigUInt64BE(0) : null;
    }
    
    /**
     * 读取UTF8字符串
     * @param length 字符串的字节长度
     * @returns 读取的字符串，如果缓冲区不足则返回null
     */
    readString(length: number): string | null {
        const data = this.read(length);
        return data ? data.toString('utf8') : null;
    }
}

// 协议解析器类
class ProtocolParser {
    // 解析状态
    private state: 'HEADER' | 'META' | 'FILE' = 'HEADER';
    // 缓冲区管理器
    private buffer = new BufferManager();
    // 当前处理的文件元数据
    private currentFile?: FileMeta;
    // 剩余文件数量
    private remainingFiles = 0;
    // 基础目录
    private baseDir: string;
    // 当前文件夹名
    private folderName = '';
    // 连接标识符
    private connectionId: string;
    
    /**
     * 构造函数
     * @param baseDir 文件保存的基础目录
     * @param connectionId 连接标识符
     */
    constructor(baseDir: string, connectionId: string) {
        this.baseDir = baseDir;
        this.connectionId = connectionId;
        console.log(`[${connectionId}] 新连接建立`);
    }
    
    /**
     * 处理接收到的数据
     * @param data 接收到的数据块
     */
    process(data: Buffer): void {
        // 追加数据到缓冲区
        this.buffer.append(data);
        console.log(`[${this.connectionId}] 接收到${data.length}字节数据，当前缓冲区大小: ${this.buffer.length}字节`);
        
        // 持续处理，直到缓冲区数据不足或处理完成
        let processed = true;
        while (processed) {
            processed = false;
            
            switch (this.state) {
                case 'HEADER':
                    processed = this.processHeader();
                    break;
                case 'META':
                    processed = this.processMeta();
                    break;
                case 'FILE':
                    processed = this.processFile();
                    break;
            }
        }
    }
    
    /**
     * 处理协议头
     * @returns 是否成功处理
     */
    private processHeader(): boolean {
        // 读取协议版本号
        const version = this.buffer.readUInt8();
        if (version === null) return false;
        
        if (version !== 0x01) {
            throw new Error(`[${this.connectionId}] 协议版本错误: ${version}`);
        }
        
        console.log(`[${this.connectionId}] 协议版本: ${version}`);
        this.state = 'META';
        return true;
    }
    
    /**
     * 处理元数据
     * @returns 是否成功处理
     */
    private processMeta(): boolean {
        // 读取文件夹名长度
        const nameLen = this.buffer.readUInt16BE();
        if (nameLen === null) return false;
        
        // 读取文件夹名
        const folderName = this.buffer.readString(nameLen);
        if (folderName === null) return false;
        
        // 读取文件总数
        const fileCount = this.buffer.readBigUInt64BE();
        if (fileCount === null) return false;
        
        this.folderName = folderName;
        this.remainingFiles = Number(fileCount);
        
        console.log(`[${this.connectionId}] 文件夹: ${folderName}, 文件总数: ${this.remainingFiles}`);
        
        // 创建目标文件夹
        this.createFolder(folderName);
        
        this.state = 'FILE';
        return true;
    }
    
    /**
     * 处理文件数据
     * @returns 是否成功处理
     */
    private processFile(): boolean {
        // 如果当前没有处理中的文件，开始处理新文件
        if (!this.currentFile) {
            // 读取文件标识
            const mark = this.buffer.readUInt8();
            if (mark === null) return false;
            
            if (mark !== 0x02) {
                throw new Error(`[${this.connectionId}] 文件标识错误: ${mark}`);
            }
            
            // 读取路径长度
            const pathLen = this.buffer.readUInt16BE();
            if (pathLen === null) return false;
            
            // 读取文件路径
            const filePath = this.buffer.readString(pathLen);
            if (filePath === null) return false;
            
            // 读取文件大小
            const fileSize = this.buffer.readBigUInt64BE();
            if (fileSize === null) return false;
            
            // 创建文件元数据
            this.currentFile = {
                path: filePath,
                size: Number(fileSize),
                received: 0
            };
            
            console.log(`[${this.connectionId}] 开始接收文件: ${filePath}, 大小: ${fileSize}字节`);
            
            // 创建文件和目录
            this.createFile(this.currentFile);
            
            return true;
        }
        
        // 处理文件内容
        const remain = this.currentFile.size - this.currentFile.received;
        if (remain <= 0) {
            // 文件已接收完毕，关闭流
            this.closeCurrentFile();
            return true;
        }
        
        // 计算本次可读取的数据量
        const readSize = Math.min(remain, this.buffer.length);
        if (readSize <= 0) return false;
        
        // 读取数据块
        const chunk = this.buffer.read(readSize);
        if (!chunk) return false;
        
        // 写入文件
        if (this.currentFile.writeStream) {
            this.currentFile.writeStream.write(chunk);
            this.currentFile.received += chunk.length;
            
            // 打印进度
            const progress = Math.floor((this.currentFile.received / this.currentFile.size) * 100);
            if (progress % 10 === 0 || this.currentFile.received === this.currentFile.size) {
                console.log(`[${this.connectionId}] 文件: ${this.currentFile.path} 进度: ${progress}%`);
            }
            
            // 检查是否接收完毕
            if (this.currentFile.received >= this.currentFile.size) {
                this.closeCurrentFile();
            }
        }
        
        return true;
    }
    
    /**
     * 关闭当前文件
     */
    private closeCurrentFile(): void {
        if (this.currentFile && this.currentFile.writeStream) {
            this.currentFile.writeStream.end();
            console.log(`[${this.connectionId}] 文件接收完成: ${this.currentFile.path}, 大小: ${this.currentFile.received}字节`);
            
            // 校验文件大小
            if (this.currentFile.received !== this.currentFile.size) {
                console.error(`[${this.connectionId}] 文件大小不匹配: ${this.currentFile.path}, 预期: ${this.currentFile.size}, 实际: ${this.currentFile.received}`);
            }
            
            this.remainingFiles--;
            console.log(`[${this.connectionId}] 剩余文件数: ${this.remainingFiles}`);
            
            if (this.remainingFiles <= 0) {
                console.log(`[${this.connectionId}] 所有文件接收完成`);
            }
            
            this.currentFile = undefined;
        }
    }
    
    /**
     * 创建文件夹
     * @param folderName 文件夹名
     */
    private createFolder(folderName: string): void {
        const targetDir = path.join(this.baseDir, folderName);
        
        // 安全检查：确保目标路径在基础目录内
        const safePath = path.resolve(targetDir);
        if (!safePath.startsWith(this.baseDir)) {
            throw new Error(`[${this.connectionId}] 路径越界攻击尝试: ${targetDir}`);
        }
        
        // 创建目录
        if (!fs.existsSync(targetDir)) {
            fs.mkdirSync(targetDir, { recursive: true });
            console.log(`[${this.connectionId}] 创建目录: ${targetDir}`);
        }
    }
    
    /**
     * 创建文件
     * @param fileMeta 文件元数据
     */
    private createFile(fileMeta: FileMeta): void {
        // 构建完整路径
        const fullPath = path.join(this.baseDir, this.folderName, fileMeta.path);
        
        // 安全检查：确保目标路径在基础目录内
        const safePath = path.resolve(fullPath);
        if (!safePath.startsWith(this.baseDir)) {
            throw new Error(`[${this.connectionId}] 路径越界攻击尝试: ${fullPath}`);
        }
        
        // 创建目录结构
        const dirPath = path.dirname(fullPath);
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
            console.log(`[${this.connectionId}] 创建目录: ${dirPath}`);
        }
        
        // 创建文件写入流
        fileMeta.writeStream = fs.createWriteStream(fullPath);
        console.log(`[${this.connectionId}] 创建文件: ${fullPath}`);
        
        // 处理写入错误
        fileMeta.writeStream.on('error', (err) => {
            console.error(`[${this.connectionId}] 文件写入错误: ${fullPath}`, err);
        });
    }
    
    /**
     * 关闭解析器，释放资源
     */
    close(): void {
        if (this.currentFile && this.currentFile.writeStream) {
            this.currentFile.writeStream.end();
        }
        console.log(`[${this.connectionId}] 连接关闭`);
    }
}

// 创建TCP服务器
const server = net.createServer((socket) => {
    // 为每个连接生成唯一ID
    const connectionId = `conn_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
    
    // 创建协议解析器
    const parser = new ProtocolParser(BASE_DIR, connectionId);
    
    // 设置编码
    socket.setNoDelay(true);
    
    // 接收数据
    socket.on('data', (data) => {
        try {
            parser.process(data);
        } catch (err) {
            console.error(`[${connectionId}] 处理数据错误:`, err);
            socket.end();
        }
    });
    
    // 连接关闭
    socket.on('close', () => {
        parser.close();
    });
    
    // 错误处理
    socket.on('error', (err) => {
        console.error(`[${connectionId}] 连接错误:`, err);
        parser.close();
    });
    
    // 设置超时
    socket.setTimeout(30000); // 30秒超时
    socket.on('timeout', () => {
        console.warn(`[${connectionId}] 连接超时`);
        socket.end();
    });
});

// 启动服务器
server.listen(PORT, () => {
    console.log(`文件传输服务器已启动，监听端口: ${PORT}`);
    console.log(`文件将保存到: ${BASE_DIR}`);
});

// 错误处理
server.on('error', (err) => {
    console.error('服务器错误:', err);
});

// 处理进程退出
process.on('SIGINT', () => {
    console.log('正在关闭服务器...');
    server.close(() => {
        console.log('服务器已关闭');
        process.exit(0);
    });
});
